{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<font color=\"red\">注</font>: 使用 tensorboard 可视化需要安装 tensorflow (TensorBoard依赖于tensorflow库，可以任意安装tensorflow的gpu/cpu版本)\n",
    "\n",
    "```shell\n",
    "pip install tensorflow-cpu\n",
    "```"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2024-07-24T02:58:47.902275Z",
     "start_time": "2024-07-24T02:58:42.922336100Z"
    },
    "execution": {
     "iopub.execute_input": "2025-02-02T16:14:01.881967Z",
     "iopub.status.busy": "2025-02-02T16:14:01.881428Z",
     "iopub.status.idle": "2025-02-02T16:14:05.107866Z",
     "shell.execute_reply": "2025-02-02T16:14:05.106967Z",
     "shell.execute_reply.started": "2025-02-02T16:14:01.881931Z"
    },
    "tags": []
   },
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "/usr/local/lib/python3.10/site-packages/tqdm/auto.py:21: TqdmWarning: IProgress not found. Please update jupyter and ipywidgets. See https://ipywidgets.readthedocs.io/en/stable/user_install.html\n",
      "  from .autonotebook import tqdm as notebook_tqdm\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "sys.version_info(major=3, minor=10, micro=14, releaselevel='final', serial=0)\n",
      "matplotlib 3.10.0\n",
      "numpy 1.26.4\n",
      "pandas 2.2.3\n",
      "sklearn 1.6.0\n",
      "torch 2.5.1+cu124\n",
      "cuda:0\n"
     ]
    }
   ],
   "source": [
    "import matplotlib as mpl\n",
    "import matplotlib.pyplot as plt\n",
    "%matplotlib inline\n",
    "import numpy as np\n",
    "import sklearn\n",
    "import pandas as pd\n",
    "import os\n",
    "import sys\n",
    "import time\n",
    "from tqdm.auto import tqdm\n",
    "import torch\n",
    "import torch.nn as nn\n",
    "import torch.nn.functional as F\n",
    "\n",
    "print(sys.version_info)\n",
    "for module in mpl, np, pd, sklearn, torch:\n",
    "    print(module.__name__, module.__version__)\n",
    "    \n",
    "device = torch.device(\"cuda:0\") if torch.cuda.is_available() else torch.device(\"cpu\")\n",
    "print(device)\n",
    "\n",
    "seed = 42\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 数据准备\n",
    "\n",
    "https://www.kaggle.com/competitions/cifar-10/data\n",
    "\n",
    "```shell\n",
    "$ tree -L 1 cifar-10                                    \n",
    "cifar-10\n",
    "├── sampleSubmission.csv\n",
    "├── test\n",
    "├── train\n",
    "└── trainLabels.csv\n",
    "```"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2024-07-24T02:58:50.165844600Z",
     "start_time": "2024-07-24T02:58:47.909271700Z"
    },
    "ExecutionIndicator": {
     "show": true
    },
    "execution": {
     "iopub.execute_input": "2025-02-02T16:14:14.022431Z",
     "iopub.status.busy": "2025-02-02T16:14:14.021790Z",
     "iopub.status.idle": "2025-02-02T16:14:17.175786Z",
     "shell.execute_reply": "2025-02-02T16:14:17.174793Z",
     "shell.execute_reply.started": "2025-02-02T16:14:14.022397Z"
    },
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[(PosixPath('competitions/cifar-10/train/1.png'), 'frog'),\n",
      " (PosixPath('competitions/cifar-10/train/2.png'), 'truck'),\n",
      " (PosixPath('competitions/cifar-10/train/3.png'), 'truck'),\n",
      " (PosixPath('competitions/cifar-10/train/4.png'), 'deer'),\n",
      " (PosixPath('competitions/cifar-10/train/5.png'), 'automobile')]\n",
      "[(PosixPath('competitions/cifar-10/test/1.png'), 'cat'),\n",
      " (PosixPath('competitions/cifar-10/test/2.png'), 'cat'),\n",
      " (PosixPath('competitions/cifar-10/test/3.png'), 'cat'),\n",
      " (PosixPath('competitions/cifar-10/test/4.png'), 'cat'),\n",
      " (PosixPath('competitions/cifar-10/test/5.png'), 'cat')]\n",
      "50000 300000\n"
     ]
    }
   ],
   "source": [
    "from pathlib import Path\n",
    "\n",
    "DATA_DIR = Path(\".\")\n",
    "DATA_DIR1 =Path(\"competitions/cifar-10/\")\n",
    "train_lables_file = DATA_DIR / \"trainLabels.csv\"\n",
    "test_csv_file = DATA_DIR / \"sampleSubmission.csv\" #测试集模板csv文件\n",
    "train_folder = DATA_DIR1 / \"train\"\n",
    "test_folder = DATA_DIR1 / \"test\"\n",
    "\n",
    "#所有的类别\n",
    "class_names = [\n",
    "    'airplane',\n",
    "    'automobile',\n",
    "    'bird',\n",
    "    'cat',\n",
    "    'deer',\n",
    "    'dog',\n",
    "    'frog',\n",
    "    'horse',\n",
    "    'ship',\n",
    "    'truck',\n",
    "]\n",
    "\n",
    "def parse_csv_file(filepath, folder):\n",
    "    \"\"\"Parses csv files into (filename(path), label) format\"\"\"\n",
    "    results = []\n",
    "    #读取所有行\n",
    "    with open(filepath, 'r') as f:\n",
    "#         lines = f.readlines()  为什么加[1:]，可以试这个\n",
    "        #第一行不需要，因为第一行是标签\n",
    "        lines = f.readlines()[1:] \n",
    "    for line in lines:#依次去取每一行\n",
    "        image_id, label_str = line.strip('\\n').split(',')\n",
    "        image_full_path = folder / f\"{image_id}.png\"\n",
    "        results.append((image_full_path, label_str)) #得到对应图片的路径和分类\n",
    "    return results\n",
    "\n",
    "#解析对应的文件夹\n",
    "train_labels_info = parse_csv_file(train_lables_file, train_folder)\n",
    "test_csv_info = parse_csv_file(test_csv_file, test_folder)\n",
    "#打印\n",
    "import pprint\n",
    "pprint.pprint(train_labels_info[0:5])\n",
    "pprint.pprint(test_csv_info[0:5])\n",
    "print(len(train_labels_info), len(test_csv_info))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2024-07-24T02:58:50.267656900Z",
     "start_time": "2024-07-24T02:58:50.168844Z"
    },
    "execution": {
     "iopub.execute_input": "2025-02-02T16:14:20.167431Z",
     "iopub.status.busy": "2025-02-02T16:14:20.166961Z",
     "iopub.status.idle": "2025-02-02T16:14:20.263300Z",
     "shell.execute_reply": "2025-02-02T16:14:20.262551Z",
     "shell.execute_reply.started": "2025-02-02T16:14:20.167398Z"
    },
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "                            filepath       class\n",
      "0  competitions/cifar-10/train/1.png        frog\n",
      "1  competitions/cifar-10/train/2.png       truck\n",
      "2  competitions/cifar-10/train/3.png       truck\n",
      "3  competitions/cifar-10/train/4.png        deer\n",
      "4  competitions/cifar-10/train/5.png  automobile\n",
      "                                filepath       class\n",
      "0  competitions/cifar-10/train/45001.png       horse\n",
      "1  competitions/cifar-10/train/45002.png  automobile\n",
      "2  competitions/cifar-10/train/45003.png        deer\n",
      "3  competitions/cifar-10/train/45004.png  automobile\n",
      "4  competitions/cifar-10/train/45005.png    airplane\n",
      "                           filepath class\n",
      "0  competitions/cifar-10/test/1.png   cat\n",
      "1  competitions/cifar-10/test/2.png   cat\n",
      "2  competitions/cifar-10/test/3.png   cat\n",
      "3  competitions/cifar-10/test/4.png   cat\n",
      "4  competitions/cifar-10/test/5.png   cat\n"
     ]
    }
   ],
   "source": [
    "# train_df = pd.DataFrame(train_labels_info)\n",
    "train_df = pd.DataFrame(train_labels_info[0:45000])\n",
    "valid_df = pd.DataFrame(train_labels_info[45000:])\n",
    "test_df = pd.DataFrame(test_csv_info)\n",
    "\n",
    "train_df.columns = ['filepath', 'class']\n",
    "valid_df.columns = ['filepath', 'class']\n",
    "test_df.columns = ['filepath', 'class']\n",
    "\n",
    "print(train_df.head())\n",
    "print(valid_df.head())\n",
    "print(test_df.head())"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2024-07-24T02:58:51.728297800Z",
     "start_time": "2024-07-24T02:58:50.271655200Z"
    },
    "execution": {
     "iopub.execute_input": "2025-02-02T16:14:22.378979Z",
     "iopub.status.busy": "2025-02-02T16:14:22.378438Z",
     "iopub.status.idle": "2025-02-02T16:14:23.497162Z",
     "shell.execute_reply": "2025-02-02T16:14:23.496340Z",
     "shell.execute_reply.started": "2025-02-02T16:14:22.378945Z"
    },
    "tags": []
   },
   "outputs": [],
   "source": [
    "from PIL import Image\n",
    "from torch.utils.data import Dataset, DataLoader\n",
    "from torchvision import transforms\n",
    "\n",
    "class Cifar10Dataset(Dataset):\n",
    "    df_map = {\n",
    "        \"train\": train_df,\n",
    "        \"eval\": valid_df,\n",
    "        \"test\": test_df\n",
    "    }\n",
    "    label_to_idx = {label: idx for idx, label in enumerate(class_names)}\n",
    "    idx_to_label = {idx: label for idx, label in enumerate(class_names)}\n",
    "    def __init__(self, mode, transform=None):\n",
    "        self.df = self.df_map.get(mode, None)\n",
    "        if self.df is None:\n",
    "            raise ValueError(\"mode should be one of train, val, test, but got {}\".format(mode))\n",
    "\n",
    "        self.transform = transform\n",
    "        \n",
    "    def __getitem__(self, index):\n",
    "        img_path, label = self.df.iloc[index]\n",
    "        img = Image.open(img_path).convert('RGB')\n",
    "        # # img 转换为 channel first\n",
    "        # img = img.transpose((2, 0, 1))\n",
    "        # transform\n",
    "        img = self.transform(img)\n",
    "        # label 转换为 idx\n",
    "        label = self.label_to_idx[label]\n",
    "        return img, label\n",
    "    \n",
    "    def __len__(self):\n",
    "        return self.df.shape[0]\n",
    "    \n",
    "IMAGE_SIZE = 32\n",
    "mean, std = [0.4914, 0.4822, 0.4465], [0.247, 0.243, 0.261]\n",
    "\n",
    "transforms_train = transforms.Compose([\n",
    "        # resize\n",
    "        transforms.Resize((IMAGE_SIZE, IMAGE_SIZE)),\n",
    "        # random rotation 40\n",
    "        transforms.RandomRotation(40),\n",
    "        # horizaontal flip\n",
    "        transforms.RandomHorizontalFlip(),\n",
    "        transforms.ToTensor(),\n",
    "        transforms.Normalize(mean, std)\n",
    "    ])\n",
    "\n",
    "transforms_eval = transforms.Compose([\n",
    "        # resize\n",
    "        transforms.Resize((IMAGE_SIZE, IMAGE_SIZE)),\n",
    "        transforms.ToTensor(),\n",
    "        transforms.Normalize(mean, std)\n",
    "    ])\n",
    "\n",
    "train_ds = Cifar10Dataset(\"train\", transforms_train)\n",
    "eval_ds = Cifar10Dataset(\"eval\", transforms_eval) "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 54,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2024-07-24T02:58:51.733366Z",
     "start_time": "2024-07-24T02:58:51.732367700Z"
    },
    "ExecutionIndicator": {
     "show": true
    },
    "execution": {
     "iopub.execute_input": "2025-02-02T17:03:47.297500Z",
     "iopub.status.busy": "2025-02-02T17:03:47.297011Z",
     "iopub.status.idle": "2025-02-02T17:03:47.302288Z",
     "shell.execute_reply": "2025-02-02T17:03:47.301409Z",
     "shell.execute_reply.started": "2025-02-02T17:03:47.297469Z"
    },
    "tags": []
   },
   "outputs": [],
   "source": [
    "batch_size = 128\n",
    "train_dl = DataLoader(train_ds, batch_size=batch_size, shuffle=True, num_workers=4)   \n",
    "eval_dl = DataLoader(eval_ds, batch_size=batch_size, shuffle=False, num_workers=4)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2024-07-24T02:58:51.745560900Z",
     "start_time": "2024-07-24T02:58:51.736749700Z"
    }
   },
   "outputs": [],
   "source": [
    "# 遍历train_ds得到每张图片，计算每个通道的均值和方差\n",
    "# def cal_mean_std(ds):\n",
    "#     mean = 0.\n",
    "#     std = 0.\n",
    "#     for img, _ in ds:\n",
    "#         mean += img.mean(dim=(1, 2))\n",
    "#         std += img.std(dim=(1, 2))\n",
    "#     mean /= len(ds)\n",
    "#     std /= len(ds)\n",
    "#     return mean, std\n",
    "#\n",
    "# # 经过 normalize 后 均值为0，方差为1\n",
    "# print(cal_mean_std(train_ds))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 定义模型"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2024-07-24T03:08:26.235576800Z",
     "start_time": "2024-07-24T03:08:26.230056200Z"
    },
    "execution": {
     "iopub.execute_input": "2025-02-02T16:14:34.372217Z",
     "iopub.status.busy": "2025-02-02T16:14:34.371431Z",
     "iopub.status.idle": "2025-02-02T16:14:34.380787Z",
     "shell.execute_reply": "2025-02-02T16:14:34.379734Z",
     "shell.execute_reply.started": "2025-02-02T16:14:34.372183Z"
    },
    "tags": []
   },
   "outputs": [],
   "source": [
    "class Resdiual(nn.Module):\n",
    "    \"\"\"浅层的残差块，无bottleneck（性能限制），因为没有1*1\"\"\"\n",
    "    def __init__(self, input_channels, output_channels, use_1x1conv=False, stride=1):\n",
    "        \"\"\"\n",
    "        残差块\n",
    "        params filters: 过滤器数目，决定输出通道\n",
    "        params use_1x1conv: 是否使用 1x1 卷积，此时 stride=2，进行降采样\n",
    "        params strides: 步长，默认为1，当降采样的时候设置为2\n",
    "        \"\"\"\n",
    "        super().__init__()\n",
    "        self.conv1 = nn.Conv2d(\n",
    "            in_channels=input_channels,\n",
    "            out_channels=output_channels,\n",
    "            kernel_size=3,\n",
    "            stride=stride,\n",
    "            padding=1,\n",
    "        )\n",
    "        self.conv2 = nn.Conv2d(\n",
    "            in_channels=output_channels,\n",
    "            out_channels=output_channels,\n",
    "            kernel_size=3,\n",
    "            stride=1,\n",
    "            padding=1,\n",
    "        )  \n",
    "        if use_1x1conv:\n",
    "            # skip connection 的 1x1 卷积，用于改变通道数和降采样，使得最终可以做残差连接\n",
    "            self.conv_sc = nn.Conv2d(\n",
    "                in_channels=input_channels,\n",
    "                out_channels=output_channels,\n",
    "                kernel_size=1,\n",
    "                stride=stride,\n",
    "            )\n",
    "        else:\n",
    "            self.conv_sc = None\n",
    "        \n",
    "        self.bn1 = nn.BatchNorm2d(output_channels, eps=1e-5, momentum=0.9)\n",
    "        self.bn2 = nn.BatchNorm2d(output_channels, eps=1e-5, momentum=0.9)\n",
    "        \n",
    "    def forward(self, inputs):\n",
    "        \"\"\"forward\"\"\"\n",
    "        flow = F.relu(self.bn1(self.conv1(inputs))) #卷积->BN->ReLU\n",
    "        flow = self.bn2(self.conv2(flow)) #卷积->BN\n",
    "        if self.conv_sc:#如果有1x1卷积，就用1x1卷积\n",
    "            inputs = self.conv_sc(inputs)\n",
    "        return F.relu(flow + inputs) #残差连接->ReLU\n",
    "    \n",
    "#如果stride=2，则输出尺寸减半,如果stride=1，则输出尺寸不变"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2024-07-24T03:08:28.540154900Z",
     "start_time": "2024-07-24T03:08:28.534159100Z"
    },
    "execution": {
     "iopub.execute_input": "2025-02-02T16:14:45.750430Z",
     "iopub.status.busy": "2025-02-02T16:14:45.749932Z",
     "iopub.status.idle": "2025-02-02T16:14:45.756913Z",
     "shell.execute_reply": "2025-02-02T16:14:45.756105Z",
     "shell.execute_reply.started": "2025-02-02T16:14:45.750397Z"
    },
    "tags": []
   },
   "outputs": [],
   "source": [
    "class ResdiualBlock(nn.Module):\n",
    "    \"\"\"若干个 Resdiual 模块堆叠在一起，通常在第一个模块给 skip connection 使用 1x1conv with stride=2\"\"\"\n",
    "    def __init__(self, input_channels, output_channels, num, is_first=False):\n",
    "        \"\"\"\n",
    "        params filters: 过滤器数目\n",
    "        params num: 堆叠几个 Resdiual 模块\n",
    "        params is_first: 是不是第一个block。 最上面一层 Resdiual 的 stride=1,is_first=False,图像尺寸减半，False图像尺寸不变\n",
    "        \"\"\"\n",
    "        super().__init__()\n",
    "        self.model = nn.Sequential() # 用于存放 Resdiual 模块\n",
    "        self.model.append(Resdiual(\n",
    "            input_channels=input_channels, \n",
    "            output_channels=output_channels, \n",
    "            use_1x1conv=True,\n",
    "            stride=1 if is_first else 2\n",
    "            )) # 第一个 Resdiual 模块\n",
    "        for _ in range(1, num):\n",
    "            # 堆叠 num 个 Resdiual 模块\n",
    "            self.model.append(Resdiual(\n",
    "                input_channels=output_channels, \n",
    "                output_channels=output_channels,\n",
    "                use_1x1conv=False, stride=1\n",
    "                ))\n",
    "    def forward(self, inputs):\n",
    "        return self.model(inputs)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 53,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2024-07-24T03:08:32.031619500Z",
     "start_time": "2024-07-24T03:08:31.738966Z"
    },
    "ExecutionIndicator": {
     "show": true
    },
    "execution": {
     "iopub.execute_input": "2025-02-02T17:03:42.042386Z",
     "iopub.status.busy": "2025-02-02T17:03:42.041888Z",
     "iopub.status.idle": "2025-02-02T17:03:42.051564Z",
     "shell.execute_reply": "2025-02-02T17:03:42.050787Z",
     "shell.execute_reply.started": "2025-02-02T17:03:42.042352Z"
    },
    "tags": []
   },
   "outputs": [],
   "source": [
    "class ResNetForCifar10(nn.Module):\n",
    "    def __init__(self, n=3, num_classes=10):\n",
    "        \"\"\"\n",
    "        params units: 预测类别的数目\n",
    "        \"\"\"\n",
    "        super().__init__()\n",
    "        self.model = nn.Sequential(\n",
    "            nn.Conv2d(\n",
    "                in_channels=3,\n",
    "                out_channels=16,\n",
    "                kernel_size=3,\n",
    "                stride=1,\n",
    "            ),  #模拟论文中的7*7卷积\n",
    "            nn.BatchNorm2d(16, momentum=0.9, eps=1e-5),\n",
    "            nn.ReLU(),\n",
    "            ResdiualBlock(input_channels=16, output_channels=32, num=2, is_first=True),  # conv2_x\n",
    "            ResdiualBlock(input_channels=32, output_channels=64, num=2),  # conv3_x\n",
    "            ResdiualBlock(input_channels=64, output_channels=128, num=3),  # conv3_x\n",
    "            ResdiualBlock(input_channels=128, output_channels=256, num=2),  # conv3_x\n",
    "            # average pool\n",
    "            nn.AdaptiveAvgPool2d((1, 1)), #无论输入图片大小，输出都是1x1，把width和height压缩为1\n",
    "            nn.Flatten(),\n",
    "            # fully connected\n",
    "            nn.Linear(in_features=256, out_features=num_classes),\n",
    "            )\n",
    "        \n",
    "        self.init_weights()\n",
    "        \n",
    "    def init_weights(self):\n",
    "        \"\"\"使用 kaiming 均匀分布来初始化全连接层、卷积层的权重 W\"\"\"\n",
    "        for m in self.modules():\n",
    "            if isinstance(m, (nn.Linear, nn.Conv2d)):\n",
    "                nn.init.kaiming_uniform_(m.weight)\n",
    "                nn.init.zeros_(m.bias)\n",
    "        \n",
    "    def forward(self, inputs):\n",
    "        return self.model(inputs)\n",
    "\n",
    "\n",
    "# for key, value in ResNetForCifar10(num_classes=len(class_names)).named_parameters():\n",
    "#     print(f\"{key:^40}paramerters num: {np.prod(value.shape)}\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 30,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2024-07-24T03:01:11.702604300Z",
     "start_time": "2024-07-24T03:01:11.425889Z"
    },
    "collapsed": false,
    "execution": {
     "iopub.execute_input": "2025-02-02T16:37:42.281083Z",
     "iopub.status.busy": "2025-02-02T16:37:42.280539Z",
     "iopub.status.idle": "2025-02-02T16:37:42.682636Z",
     "shell.execute_reply": "2025-02-02T16:37:42.681835Z",
     "shell.execute_reply.started": "2025-02-02T16:37:42.281049Z"
    },
    "jupyter": {
     "outputs_hidden": false
    },
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Total trainable parameters: 21260842\n"
     ]
    }
   ],
   "source": [
    "#模型总参数量\n",
    "total_params = sum(p.numel() for p in ResNetForCifar10(num_classes=len(class_names)).parameters() if p.requires_grad)\n",
    "print(f\"Total trainable parameters: {total_params}\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 31,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2024-07-24T03:08:35.847987800Z",
     "start_time": "2024-07-24T03:08:35.516829900Z"
    },
    "collapsed": false,
    "execution": {
     "iopub.execute_input": "2025-02-02T16:37:44.424011Z",
     "iopub.status.busy": "2025-02-02T16:37:44.423484Z",
     "iopub.status.idle": "2025-02-02T16:37:44.779445Z",
     "shell.execute_reply": "2025-02-02T16:37:44.778475Z",
     "shell.execute_reply.started": "2025-02-02T16:37:44.423977Z"
    },
    "jupyter": {
     "outputs_hidden": false
    },
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "torch.Size([1, 10])\n"
     ]
    }
   ],
   "source": [
    "#随机一个样本，喂入model\n",
    "sample_data = torch.randn(1, 3, 32, 32)\n",
    "model=ResNetForCifar10(num_classes=len(class_names))\n",
    "output=model(sample_data)\n",
    "print(output.shape)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 训练\n",
    "\n",
    "pytorch的训练需要自行实现，包括\n",
    "1. 定义损失函数\n",
    "2. 定义优化器\n",
    "3. 定义训练步\n",
    "4. 训练"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {
    "execution": {
     "iopub.execute_input": "2025-02-02T16:15:37.595436Z",
     "iopub.status.busy": "2025-02-02T16:15:37.594950Z",
     "iopub.status.idle": "2025-02-02T16:15:37.633392Z",
     "shell.execute_reply": "2025-02-02T16:15:37.632652Z",
     "shell.execute_reply.started": "2025-02-02T16:15:37.595403Z"
    },
    "tags": []
   },
   "outputs": [],
   "source": [
    "from sklearn.metrics import accuracy_score\n",
    "\n",
    "@torch.no_grad()\n",
    "def evaluating(model, dataloader, loss_fct):\n",
    "    loss_list = []\n",
    "    pred_list = []\n",
    "    label_list = []\n",
    "    for datas, labels in dataloader:\n",
    "        datas = datas.to(device)\n",
    "        labels = labels.to(device)\n",
    "        # 前向计算\n",
    "        logits = model(datas)\n",
    "        loss = loss_fct(logits, labels)         # 验证集损失\n",
    "        loss_list.append(loss.item())\n",
    "        \n",
    "        preds = logits.argmax(axis=-1)    # 验证集预测\n",
    "        pred_list.extend(preds.cpu().numpy().tolist())\n",
    "        label_list.extend(labels.cpu().numpy().tolist())\n",
    "        \n",
    "    acc = accuracy_score(label_list, pred_list)\n",
    "    return np.mean(loss_list), acc\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "jp-MarkdownHeadingCollapsed": true,
    "tags": []
   },
   "source": [
    "### TensorBoard 可视化\n",
    "\n",
    "\n",
    "训练过程中可以使用如下命令启动tensorboard服务。\n",
    "\n",
    "```shell\n",
    "tensorboard \\\n",
    "    --logdir=runs \\     # log 存放路径\n",
    "    --host 0.0.0.0 \\    # ip\n",
    "    --port 8848         # 端口\n",
    "```"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {
    "execution": {
     "iopub.execute_input": "2025-02-02T16:15:39.711919Z",
     "iopub.status.busy": "2025-02-02T16:15:39.711007Z",
     "iopub.status.idle": "2025-02-02T16:15:39.799816Z",
     "shell.execute_reply": "2025-02-02T16:15:39.799014Z",
     "shell.execute_reply.started": "2025-02-02T16:15:39.711882Z"
    },
    "tags": []
   },
   "outputs": [],
   "source": [
    "from torch.utils.tensorboard import SummaryWriter\n",
    "\n",
    "\n",
    "class TensorBoardCallback:\n",
    "    def __init__(self, log_dir, flush_secs=10):\n",
    "        \"\"\"\n",
    "        Args:\n",
    "            log_dir (str): dir to write log.\n",
    "            flush_secs (int, optional): write to dsk each flush_secs seconds. Defaults to 10.\n",
    "        \"\"\"\n",
    "        self.writer = SummaryWriter(log_dir=log_dir, flush_secs=flush_secs)\n",
    "\n",
    "    def draw_model(self, model, input_shape):\n",
    "        self.writer.add_graph(model, input_to_model=torch.randn(input_shape))\n",
    "        \n",
    "    def add_loss_scalars(self, step, loss, val_loss):\n",
    "        self.writer.add_scalars(\n",
    "            main_tag=\"training/loss\", \n",
    "            tag_scalar_dict={\"loss\": loss, \"val_loss\": val_loss},\n",
    "            global_step=step,\n",
    "            )\n",
    "        \n",
    "    def add_acc_scalars(self, step, acc, val_acc):\n",
    "        self.writer.add_scalars(\n",
    "            main_tag=\"training/accuracy\",\n",
    "            tag_scalar_dict={\"accuracy\": acc, \"val_accuracy\": val_acc},\n",
    "            global_step=step,\n",
    "        )\n",
    "        \n",
    "    def add_lr_scalars(self, step, learning_rate):\n",
    "        self.writer.add_scalars(\n",
    "            main_tag=\"training/learning_rate\",\n",
    "            tag_scalar_dict={\"learning_rate\": learning_rate},\n",
    "            global_step=step,\n",
    "            \n",
    "        )\n",
    "    \n",
    "    def __call__(self, step, **kwargs):\n",
    "        # add loss\n",
    "        loss = kwargs.pop(\"loss\", None)\n",
    "        val_loss = kwargs.pop(\"val_loss\", None)\n",
    "        if loss is not None and val_loss is not None:\n",
    "            self.add_loss_scalars(step, loss, val_loss)\n",
    "        # add acc\n",
    "        acc = kwargs.pop(\"acc\", None)\n",
    "        val_acc = kwargs.pop(\"val_acc\", None)\n",
    "        if acc is not None and val_acc is not None:\n",
    "            self.add_acc_scalars(step, acc, val_acc)\n",
    "        # add lr\n",
    "        learning_rate = kwargs.pop(\"lr\", None)\n",
    "        if learning_rate is not None:\n",
    "            self.add_lr_scalars(step, learning_rate)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "jp-MarkdownHeadingCollapsed": true,
    "tags": []
   },
   "source": [
    "### Save Best\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {
    "execution": {
     "iopub.execute_input": "2025-02-02T16:15:42.464648Z",
     "iopub.status.busy": "2025-02-02T16:15:42.463966Z",
     "iopub.status.idle": "2025-02-02T16:15:42.471658Z",
     "shell.execute_reply": "2025-02-02T16:15:42.470837Z",
     "shell.execute_reply.started": "2025-02-02T16:15:42.464594Z"
    },
    "tags": []
   },
   "outputs": [],
   "source": [
    "class SaveCheckpointsCallback:\n",
    "    def __init__(self, save_dir, save_step=5000, save_best_only=True):\n",
    "        \"\"\"\n",
    "        Save checkpoints each save_epoch epoch. \n",
    "        We save checkpoint by epoch in this implementation.\n",
    "        Usually, training scripts with pytorch evaluating model and save checkpoint by step.\n",
    "\n",
    "        Args:\n",
    "            save_dir (str): dir to save checkpoint\n",
    "            save_epoch (int, optional): the frequency to save checkpoint. Defaults to 1.\n",
    "            save_best_only (bool, optional): If True, only save the best model or save each model at every epoch.\n",
    "        \"\"\"\n",
    "        self.save_dir = save_dir\n",
    "        self.save_step = save_step\n",
    "        self.save_best_only = save_best_only\n",
    "        self.best_metrics = -1\n",
    "        \n",
    "        # mkdir\n",
    "        if not os.path.exists(self.save_dir):\n",
    "            os.mkdir(self.save_dir)\n",
    "        \n",
    "    def __call__(self, step, state_dict, metric=None):\n",
    "        if step % self.save_step > 0:\n",
    "            return\n",
    "        \n",
    "        if self.save_best_only:\n",
    "            assert metric is not None\n",
    "            if metric >= self.best_metrics:\n",
    "                # save checkpoints\n",
    "                torch.save(state_dict, os.path.join(self.save_dir, \"best.ckpt\"))\n",
    "                # update best metrics\n",
    "                self.best_metrics = metric\n",
    "        else:\n",
    "            torch.save(state_dict, os.path.join(self.save_dir, f\"{step}.ckpt\"))\n",
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "jp-MarkdownHeadingCollapsed": true,
    "tags": []
   },
   "source": [
    "### Early Stop"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "metadata": {
    "execution": {
     "iopub.execute_input": "2025-02-02T16:15:54.789946Z",
     "iopub.status.busy": "2025-02-02T16:15:54.789449Z",
     "iopub.status.idle": "2025-02-02T16:15:54.796063Z",
     "shell.execute_reply": "2025-02-02T16:15:54.795079Z",
     "shell.execute_reply.started": "2025-02-02T16:15:54.789912Z"
    },
    "tags": []
   },
   "outputs": [],
   "source": [
    "class EarlyStopCallback:\n",
    "    def __init__(self, patience=5, min_delta=0.01):\n",
    "        \"\"\"\n",
    "\n",
    "        Args:\n",
    "            patience (int, optional): Number of epochs with no improvement after which training will be stopped.. Defaults to 5.\n",
    "            min_delta (float, optional): Minimum change in the monitored quantity to qualify as an improvement, i.e. an absolute \n",
    "                change of less than min_delta, will count as no improvement. Defaults to 0.01.\n",
    "        \"\"\"\n",
    "        self.patience = patience\n",
    "        self.min_delta = min_delta\n",
    "        self.best_metric = -1\n",
    "        self.counter = 0\n",
    "        \n",
    "    def __call__(self, metric):\n",
    "        if metric >= self.best_metric + self.min_delta:\n",
    "            # update best metric\n",
    "            self.best_metric = metric\n",
    "            # reset counter \n",
    "            self.counter = 0\n",
    "        else: \n",
    "            self.counter += 1\n",
    "            \n",
    "    @property\n",
    "    def early_stop(self):\n",
    "        return self.counter >= self.patience\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## training"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 55,
   "metadata": {
    "ExecutionIndicator": {
     "show": true
    },
    "execution": {
     "iopub.execute_input": "2025-02-02T17:03:51.914969Z",
     "iopub.status.busy": "2025-02-02T17:03:51.914206Z",
     "iopub.status.idle": "2025-02-02T17:08:37.376196Z",
     "shell.execute_reply": "2025-02-02T17:08:37.374796Z",
     "shell.execute_reply.started": "2025-02-02T17:03:51.914935Z"
    },
    "tags": []
   },
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      " 87%|████████▋ | 9152/10560 [04:44<00:43, 32.13it/s, epoch=25]"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Early stop at epoch 26 / global_step 9152\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "\n"
     ]
    }
   ],
   "source": [
    "# 训练\n",
    "def training(\n",
    "    model, \n",
    "    train_loader, \n",
    "    val_loader, \n",
    "    epoch, \n",
    "    loss_fct, \n",
    "    optimizer, \n",
    "    tensorboard_callback=None,\n",
    "    save_ckpt_callback=None,\n",
    "    early_stop_callback=None,\n",
    "    eval_step=500,\n",
    "    ):\n",
    "    record_dict = {\n",
    "        \"train\": [],\n",
    "        \"val\": []\n",
    "    }\n",
    "    \n",
    "    global_step = 0\n",
    "    model.train()\n",
    "    with tqdm(total=epoch * len(train_loader)) as pbar:\n",
    "        for epoch_id in range(epoch):\n",
    "            # training\n",
    "            for datas, labels in train_loader:\n",
    "                datas = datas.to(device)\n",
    "                labels = labels.to(device)\n",
    "                # 梯度清空\n",
    "                optimizer.zero_grad()\n",
    "                # 模型前向计算\n",
    "                logits = model(datas)\n",
    "                # 计算损失\n",
    "                loss = loss_fct(logits, labels)\n",
    "                # 梯度回传\n",
    "                loss.backward()\n",
    "                # 调整优化器，包括学习率的变动等\n",
    "                optimizer.step()\n",
    "                preds = logits.argmax(axis=-1)\n",
    "            \n",
    "                acc = accuracy_score(labels.cpu().numpy(), preds.cpu().numpy())    \n",
    "                loss = loss.cpu().item()\n",
    "                # record\n",
    "                \n",
    "                record_dict[\"train\"].append({\n",
    "                    \"loss\": loss, \"acc\": acc, \"step\": global_step\n",
    "                })\n",
    "                \n",
    "                # evaluating\n",
    "                if global_step % eval_step == 0:\n",
    "                    model.eval()\n",
    "                    val_loss, val_acc = evaluating(model, val_loader, loss_fct)\n",
    "                    record_dict[\"val\"].append({\n",
    "                        \"loss\": val_loss, \"acc\": val_acc, \"step\": global_step\n",
    "                    })\n",
    "                    model.train()\n",
    "                    \n",
    "                    # 1. 使用 tensorboard 可视化\n",
    "                    if tensorboard_callback is not None:\n",
    "                        tensorboard_callback(\n",
    "                            global_step, \n",
    "                            loss=loss, val_loss=val_loss,\n",
    "                            acc=acc, val_acc=val_acc,\n",
    "                            lr=optimizer.param_groups[0][\"lr\"],\n",
    "                            )\n",
    "                \n",
    "                    # 2. 保存模型权重 save model checkpoint\n",
    "                    if save_ckpt_callback is not None:\n",
    "                        save_ckpt_callback(global_step, model.state_dict(), metric=val_acc)\n",
    "\n",
    "                    # 3. 早停 Early Stop\n",
    "                    if early_stop_callback is not None:\n",
    "                        early_stop_callback(val_acc)\n",
    "                        if early_stop_callback.early_stop:\n",
    "                            print(f\"Early stop at epoch {epoch_id} / global_step {global_step}\")\n",
    "                            return record_dict\n",
    "                    \n",
    "                # udate step\n",
    "                global_step += 1\n",
    "                pbar.update(1)\n",
    "                pbar.set_postfix({\"epoch\": epoch_id})\n",
    "        \n",
    "    return record_dict\n",
    "        \n",
    "\n",
    "epoch = 30\n",
    "\n",
    "model = ResNetForCifar10(num_classes=10)\n",
    "\n",
    "# 1. 定义损失函数 采用交叉熵损失\n",
    "loss_fct = nn.CrossEntropyLoss()\n",
    "# 2. 定义优化器 采用 Rmsprop\n",
    "# Optimizers specified in the torch.optim package\n",
    "# >>> # Assuming optimizer uses lr = 0.05 for all groups\n",
    "# >>> # lr = 0.05     if epoch < 30\n",
    "# >>> # lr = 0.005    if 30 <= epoch < 80\n",
    "# >>> # lr = 0.0005   if epoch >= 80\n",
    "# >>> scheduler = MultiStepLR(optimizer, milestones=[30,80], gamma=0.1)\n",
    "\n",
    "class OptimizerWithScheduler:\n",
    "    def __init__(self, parameters, lr, momentum, weight_decay):\n",
    "        self.optimizer = torch.optim.SGD(parameters, lr=lr, momentum=momentum, weight_decay=weight_decay) # 优化器\n",
    "        self.scheduler = torch.optim.lr_scheduler.MultiStepLR(self.optimizer, milestones=[32_000, 48_000], gamma=0.1) # 学习率衰减\n",
    "        \n",
    "    def step(self):\n",
    "        self.optimizer.step()\n",
    "        self.scheduler.step()\n",
    "        \n",
    "    @property\n",
    "    def param_groups(self):\n",
    "        return self.optimizer.param_groups\n",
    "        \n",
    "    def zero_grad(self):\n",
    "        self.optimizer.zero_grad()\n",
    "        \n",
    "optimizer = OptimizerWithScheduler(model.parameters(), lr=0.1, momentum=0.9, weight_decay=1e-4)\n",
    "\n",
    "# 1. tensorboard 可视化\n",
    "if not os.path.exists(\"runs\"):\n",
    "    os.mkdir(\"runs\")\n",
    "    \n",
    "exp_name = \"resnet34\"\n",
    "tensorboard_callback = TensorBoardCallback(f\"runs/{exp_name}\")\n",
    "tensorboard_callback.draw_model(model, [1, 3, IMAGE_SIZE, IMAGE_SIZE])\n",
    "# 2. save best\n",
    "if not os.path.exists(\"checkpoints\"):\n",
    "    os.makedirs(\"checkpoints\")\n",
    "save_ckpt_callback = SaveCheckpointsCallback(f\"checkpoints/{exp_name}\", save_step=len(train_dl), save_best_only=True)\n",
    "# 3. early stop\n",
    "early_stop_callback = EarlyStopCallback(patience=5)\n",
    "\n",
    "model = model.to(device)\n",
    "record = training(\n",
    "    model, \n",
    "    train_dl, \n",
    "    eval_dl, \n",
    "    epoch, \n",
    "    loss_fct, \n",
    "    optimizer, \n",
    "    tensorboard_callback=None,\n",
    "    save_ckpt_callback=save_ckpt_callback,\n",
    "    early_stop_callback=early_stop_callback,\n",
    "    eval_step=len(train_dl)\n",
    "    )"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 56,
   "metadata": {
    "execution": {
     "iopub.execute_input": "2025-02-02T17:08:41.261454Z",
     "iopub.status.busy": "2025-02-02T17:08:41.260906Z",
     "iopub.status.idle": "2025-02-02T17:08:41.546692Z",
     "shell.execute_reply": "2025-02-02T17:08:41.545926Z",
     "shell.execute_reply.started": "2025-02-02T17:08:41.261412Z"
    },
    "tags": []
   },
   "outputs": [
    {
     "data": {
      "image/png": "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",
      "text/plain": [
       "<Figure size 1000x500 with 2 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "#画线要注意的是损失是不一定在零到1之间的\n",
    "def plot_learning_curves(record_dict, sample_step=500):\n",
    "    # build DataFrame\n",
    "    train_df = pd.DataFrame(record_dict[\"train\"]).set_index(\"step\").iloc[::sample_step]\n",
    "    val_df = pd.DataFrame(record_dict[\"val\"]).set_index(\"step\")\n",
    "\n",
    "    # plot\n",
    "    fig_num = len(train_df.columns)\n",
    "    fig, axs = plt.subplots(1, fig_num, figsize=(5 * fig_num, 5))\n",
    "    for idx, item in enumerate(train_df.columns):    \n",
    "        axs[idx].plot(train_df.index, train_df[item], label=f\"train_{item}\")\n",
    "        axs[idx].plot(val_df.index, val_df[item], label=f\"val_{item}\")\n",
    "        axs[idx].grid()\n",
    "        axs[idx].legend()\n",
    "        # axs[idx].set_xticks(range(0, train_df.index[-1], 5000))\n",
    "        # axs[idx].set_xticklabels(map(lambda x: f\"{int(x/1000)}k\", range(0, train_df.index[-1], 5000)))\n",
    "        axs[idx].set_xlabel(\"step\")\n",
    "    \n",
    "    plt.show()\n",
    "\n",
    "plot_learning_curves(record, sample_step=10)  #横坐标是 steps"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 评估"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 49,
   "metadata": {
    "ExecutionIndicator": {
     "show": true
    },
    "execution": {
     "iopub.execute_input": "2025-02-02T17:01:11.226515Z",
     "iopub.status.busy": "2025-02-02T17:01:11.226012Z",
     "iopub.status.idle": "2025-02-02T17:01:12.349013Z",
     "shell.execute_reply": "2025-02-02T17:01:12.347942Z",
     "shell.execute_reply.started": "2025-02-02T17:01:11.226480Z"
    },
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "loss:     0.5586\n",
      "accuracy: 0.8094\n"
     ]
    }
   ],
   "source": [
    "# dataload for evaluating\n",
    "\n",
    "# load checkpoints\n",
    "model.load_state_dict(torch.load(f\"checkpoints/{exp_name}/best.ckpt\",weights_only=True, map_location=\"cpu\"))\n",
    "\n",
    "model.eval()\n",
    "loss, acc = evaluating(model, eval_dl, loss_fct)\n",
    "print(f\"loss:     {loss:.4f}\\naccuracy: {acc:.4f}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 推理"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# test_df\n",
    "test_ds = Cifar10Dataset(\"test\", transform=transforms_eval)\n",
    "test_dl = DataLoader(test_ds, batch_size=batch_size, shuffle=False, drop_last=False)\n",
    "\n",
    "preds_collect = []\n",
    "model.eval()\n",
    "for data, fake_label in tqdm(test_dl):\n",
    "    data = data.to(device=device)\n",
    "    logits = model(data)\n",
    "    preds = [test_ds.idx_to_label[idx] for idx in logits.argmax(axis=-1).cpu().tolist()]\n",
    "    preds_collect.extend(preds)\n",
    "    \n",
    "test_df[\"class\"] = preds_collect\n",
    "test_df.head()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 导出 submission.csv\n",
    "test_df.to_csv(\"submission.csv\", index=False)"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.10.14"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
